fix: reject impossible CALYPSO atom choices - #382
Conversation
|
Warning Review limit reachedNext included review available in 59 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Correct the empty-set validation so nested atom choices that cannot produce unique species fail before the random selection loop. Closes deepmodeling#356 Coding-Agent: Codex Codex-Version: codex-cli 0.149.1 Model: gpt-5.6-sol Reasoning-Effort: xhigh
5507103 to
d3ca156
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #382 +/- ##
==========================================
+ Coverage 84.43% 84.45% +0.01%
==========================================
Files 104 104
Lines 6110 6110
==========================================
+ Hits 5159 5160 +1
+ Misses 951 950 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Retracted. This review was produced without running the mandated /code-review fan-out (the loop skill's section 2); the substitute process used instead has since been shown to miss findings and, in one case, to state a verified-sounding falsehood. Re-reviewing properly.
wanghan-iapcm
left a comment
There was a problem hiding this comment.
You are right that (set(s) - overlap) == 0 compares a set to an int and has never fired — that part of #356's diagnosis is correct, and I confirmed the guard has been dead since it was written in 07df321 (#217, 2024-04-30). But the replacement predicate is not the right test, and turning it on is a net regression: it rejects configurations that work today while still hanging on the ones #356 is about.
The predicate is not the condition the loop needs
overlap is the intersection of all sub-lists, so overlap ⊆ L holds for every L. That makes not (set(L) - overlap) equivalent to set(L) == overlap — "some sub-list equals the global intersection". The loop below needs one element per sub-list, all distinct, which exists exactly when Hall's marriage condition holds. The two are unrelated.
Brute-forcing every family over a 3-symbol universe (399 configs, satisfiability decided by exhaustive search):
sub-lists total ok-accept ok-reject WRONGLY REJECTED WRONGLY ACCEPTED
1 7 0 0 7 0
2 49 18 3 28 0
3 343 165 51 82 45
total 399 183 54 117 45
117 of 399 (29%) are satisfiable configs the guard now rejects. 45 (11%) are impossible configs it still lets through, where the loop hangs exactly as #356 describes.
Run against the real make_calypso_task_group_from_config:
[["Li"]] d3ca156~1: SUCCESS head: ValueError
[["Li","Na","K"],["Na","K"],["K"]] d3ca156~1: ['Li','Na','K'] head: ValueError
[["Li"],["Li"],["Na","K"]] head: killed at 8s, exit 137 (still spinning)
Note the first row: every single-sub-list config is now rejected, because with one sub-list the intersection is that sub-list. name_of_atoms: [["Li","Na","K"]] — pick one species at random for a 1-species search — is the simplest use of this feature and it now fails at submit.
And the second row is the example printed in the error message itself. Details inline.
Suggested direction
Hall's condition is exact and cheap at CALYPSO species counts:
from itertools import combinations
sets = [set(s) for s in name_of_atoms]
n = len(sets)
if any(
len(set().union(*(sets[i] for i in sub))) < k
for k in range(1, n + 1)
for sub in combinations(range(n), k)
):
raise ValueError(
f"cannot pick {n} distinct species from {name_of_atoms}: "
"some group of sub-lists has fewer candidates than sub-lists"
)I checked this against exhaustive search over the same sweep: zero mismatches. Better still would be to replace the while True rejection sampling with a direct matching that constructs the assignment — then the guard is unnecessary and the hang is impossible by construction.
Merge-order warning, not a change request
PR #405 ("Enable remaining Ruff rules", open) rewrites this same line to any((set(s) - overlap) == 0 for s in name_of_atoms) — a pure map→genexp change that keeps the == 0 bug. git merge-tree confirms a direct conflict on this line. If #405 lands after this and the conflict is resolved carelessly, the always-false guard comes back. Worth sequencing them deliberately. (#383 also touches this file but only conflicts on the test file's insertion point — trivial.)
Not a problem, so nobody re-raises it
The bare ValueError is correct here. I traced the call chain — submit_concurrent_learning → workflow_concurrent_learning → make_naive_exploration_scheduler → make_calypso_task_group_from_config → set_params — and it all runs client-side during dpgen2 submit, before wf.submit(), not inside a dflow OP. FatalError would be wrong at this layer.
| @@ -122,7 +122,7 @@ def set_params( | |||
| for temp in name_of_atoms[1:]: | |||
| overlap = overlap & set(temp) | |||
|
|
|||
There was a problem hiding this comment.
Two things go wrong on this line, and they are the same root cause.
It rejects valid configurations. Since overlap ⊆ atom_choices always holds, this fires whenever a sub-list equals the global intersection. That is the shape of any "narrow the choices as you go" config, and of every single-sub-list config. Verified against the shipped API:
[["Li"]] before: SUCCESS after: ValueError
[["Li","Na"]] before: SUCCESS after: ValueError
[["Li","H"],["La","H"],["H"]] before: ok after: ValueError (Li, La, H is valid)
The example in the error message two lines below is itself valid. [[A,B,C],[B,C],[C]] assigns C→B→A. I ran the real equivalent, [["Li","Na","K"],["Na","K"],["K"]]: at d3ca156~1 it returned ['Li','Na','K']; at this head it raises. So the message has documented a legal config as forbidden since #217, and this change is what makes the code enforce that. Whatever predicate you land on, that sentence needs to go or be corrected — it is the only user-facing description of the rule, and it is wrong.
It still hangs on genuinely impossible configs. [["Li"],["Li"],["Na","K"]] has an empty global intersection, so no sub-list equals it, the guard stays silent, and the loop spins forever — I killed it at 8 seconds, exit 137. That is the same failure #356 reports, one sub-list larger. 45 of 399 swept configs behave this way.
Hall's condition is the exact test; see the review body for a drop-in that I verified has zero mismatches against exhaustive search. Whichever way you go, it would be worth putting the offending name_of_atoms and the computed intersection into the message — as written it prints neither, so a user cannot tell which sub-list tripped it.
| tgroup = make_calypso_task_group_from_config(self.config) | ||
| self.assertTrue(isinstance(tgroup, CalyTaskGroup)) | ||
|
|
||
| def test_rejects_impossible_random_atom_choices(self): |
There was a problem hiding this comment.
[["Li"],["Li"]] happens to be a case where the wrong rule and the right rule agree, so this test cannot tell them apart. I patched three different predicates into the guard and ran only this test:
PR's rule (sub-list == global intersection) PASSED
Hall's condition (correct) PASSED
"raise iff any two sub-lists are identical" (wrong) PASSED
All three. So it would not catch a wrong fix, which is the thing worth catching here.
Separately: if the guard ever regresses, this test hangs rather than fails. I reverted the predicate to the pre-PR always-false form and ran it under timeout -k 2 15:
Terminated
EXIT_CODE=124
No pytest verdict at all — assertRaisesRegex is wrapping a call that enters an unbounded while True. A wedged CI job is a worse signal than a red one.
To be fair to it, the test does pin something real: that some guard fires before the retry loop for this input. Three additions would make it discriminating and safe:
- a positive case that must succeed —
[["Li","Na","K"],["Na","K"],["K"]], or just[["Li"]]— which fails today; - a true negative with an empty intersection —
[["Li"],["Li"],["Na"]]— which currently hangs; - and asserting on the predicate directly, or adding a timeout, so a regression reports instead of wedging.
Summary
ValueErrorbefore entering the random selection loop[["Li"], ["Li"]]Tests
PYTHONPATH=tests python -m unittest -v tests.exploration.test_make_task_group_from_config.TestMakeCalyTaskGroupFromConfig.test_rejects_impossible_random_atom_choices tests.exploration.test_make_task_group_from_config.TestMakeCalyTaskGroupFromConfig.test_caly_task_groupruff format --check dpgen2/exploration/task/caly_task_group.py tests/exploration/test_make_task_group_from_config.pyisort --check-only dpgen2/exploration/task/caly_task_group.py tests/exploration/test_make_task_group_from_config.pygit diff --checkCloses #356
Coding agent: Codex
Codex version: codex-cli 0.149.0
Model: gpt-5.6-sol
Reasoning effort: xhigh